package org.greatlogic.gwtfilereaderwidget.client.itunes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EConjunction;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.ETrackKeyType;
import org.greatlogic.gwtfilereaderwidget.client.itunes.ITunesEnums.EOperator;

/*
 * Copyright 2006 Andy King (GreatLogic.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
/**
 * Provides a filter for an iTunes library, which allows tracks to be selected based upon conditions
 * applied to the filter.  After creating the Filter object against a specific library the "and" and 
 * "or" methods can be used to specify the condition that will be used when creating the resulting
 * track list.
 */
public class Filter {
//--------------------------------------------------------------------------------------------------
private Condition          _condition;
private ITunesLibrary      _library;
private ArrayList<SortDef> _sortDefList;
//==================================================================================================
private static class SortDef {
private boolean  _ascending;
private ETrackKeyType _keyType;
private SortDef(final ETrackKeyType keyType, final boolean ascending) {
  _keyType = keyType;
  _ascending = ascending;
} // SortDef()
} // class SortDef
//==================================================================================================
/**
 * Creates a new filter that can be used to apply a condition to the library.
 * @param library The library that the filter will be applied to.
 */
public Filter(final ITunesLibrary library) {
  _library = library;
  _condition = new Condition();
  _sortDefList = new ArrayList<Filter.SortDef>();
} // Filter()
//--------------------------------------------------------------------------------------------------
public Filter addFilterLine(final int openParens, final int closeParens,
                            final EConjunction conjunction, final ETrackKeyType keyType,
                            final EOperator operator) throws ITunesException {
  _condition.addNode(openParens, closeParens, conjunction, keyType, operator);
  return this;
} // addFilterLine()
//--------------------------------------------------------------------------------------------------
public Filter addFilterLine(final int openParens, final int closeParens,
                            final EConjunction conjunction, final ETrackKeyType keyType,
                            final EOperator operator, final long... operands)
  throws ITunesException {
  _condition.addNode(openParens, closeParens, conjunction, keyType, operator, operands);
  return this;
} // addFilterLine()
//--------------------------------------------------------------------------------------------------
public Filter addFilterLine(final int openParens, final int closeParens,
                            final EConjunction conjunction, final ETrackKeyType keyType,
                            final EOperator operator, final String... operands)
  throws ITunesException {
  _condition.addNode(openParens, closeParens, conjunction, keyType, operator, operands);
  return this;
} // addFilterLine()
//--------------------------------------------------------------------------------------------------
public Filter and(final ETrackKeyType keyType, final EOperator operator) throws ITunesException {
  return addFilterLine(0, 0, EConjunction.And, keyType, operator);
} // and()
//--------------------------------------------------------------------------------------------------
public Filter and(final ETrackKeyType keyType, final EOperator operator, final long... operands)
  throws ITunesException {
  return addFilterLine(0, 0, EConjunction.And, keyType, operator, operands);
} // and()
//--------------------------------------------------------------------------------------------------
public Filter and(final ETrackKeyType keyType, final EOperator operator, final String... operands)
  throws ITunesException {
  return addFilterLine(0, 0, EConjunction.And, keyType, operator, operands);
} // and()
//--------------------------------------------------------------------------------------------------
public Filter andCloseParens(final int closeParens, final ETrackKeyType keyType, final EOperator operator)
  throws ITunesException {
  return addFilterLine(0, closeParens, EConjunction.And, keyType, operator);
} // andCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter andCloseParens(final int closeParens, final ETrackKeyType keyType,
                             final EOperator operator, final long... operands)
  throws ITunesException {
  return addFilterLine(0, closeParens, EConjunction.And, keyType, operator, operands);
} // andCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter andCloseParens(final int closeParens, final ETrackKeyType keyType,
                             final EOperator operator, final String... operands)
  throws ITunesException {
  return addFilterLine(0, closeParens, EConjunction.And, keyType, operator, operands);
} // andCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter andOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator)
  throws ITunesException {
  return addFilterLine(openParens, 0, EConjunction.And, keyType, operator);
} // andOpenParens()
//--------------------------------------------------------------------------------------------------
public Filter andOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator,
                            final long... operands) throws ITunesException {
  return addFilterLine(openParens, 0, EConjunction.And, keyType, operator, operands);
} // andOpenParens()
//--------------------------------------------------------------------------------------------------
public Filter andOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator,
                            final String... operands) throws ITunesException {
  return addFilterLine(openParens, 0, EConjunction.And, keyType, operator, operands);
} // andOpenParens()
//--------------------------------------------------------------------------------------------------
private Comparator<Track> getComparator() {
  return new Comparator<Track>() {
    @Override
    public int compare(final Track track1, final Track track2) {
      int result = 0;
      for (SortDef sortDef : _sortDefList) {
        KeyAndValueEntry keyAndValueEntry1 = track1.getKeyAndValueEntry(sortDef._keyType);
        KeyAndValueEntry keyAndValueEntry2 = track2.getKeyAndValueEntry(sortDef._keyType);
        switch (sortDef._keyType.getKeyDataType()) {
          case Array:
            break;
          case Boolean:
            break;
          case Date:
            break;
          case Integer:
            result = Long.signum(keyAndValueEntry1.getValueAsLong() -
                                 keyAndValueEntry2.getValueAsLong());
            break;
          case String:
            result = keyAndValueEntry1.getValueAsString().compareToIgnoreCase(keyAndValueEntry2.getValueAsString());
            break;
          case Unknown:
            break;
        }
        if (result != 0) {
          result = sortDef._ascending ? result : -result;
          break;
        }
      }
      return result;
    }
  };
} // getComparator()
//--------------------------------------------------------------------------------------------------
public ArrayList<Track> getSortedTrackList(final Iterable<String> playlistNames,
                                           final int maxNumberOfTracks) throws ITunesException {
  ArrayList<Track> result = getTrackList(playlistNames, maxNumberOfTracks);
  Collections.sort(result, getComparator());
  return result;
} // getSortedTrackMap()
//--------------------------------------------------------------------------------------------------
/**
 * Creates a list containing all tracks that meet the conditions associated with the filter.
 * @param playlistNames The playlist names that are to be the source for the filter. If this value
 * null then the "master" playlist will be used, that is, the playlist that contains all tracks.
 * @param maxNumberOfTracks The maximum number of tracks to include in the list. If this value is
 * zero then all tracks will be included.
 * @return The list of tracks that meet the filter condition.
 */
public ArrayList<Track> getTrackList(final Iterable<String> playlistNames,
                                     final int maxNumberOfTracks) throws ITunesException {
  ArrayList<Track> result = new ArrayList<Track>();
  validate();
  TreeSet<Track> trackSet = _library.getTrackListSetUsingPlaylistNames(playlistNames);
  int numberOfTracks = 0;
  for (Track track : trackSet) {
    if (_condition.evaluate(track)) {
      result.add(track);
      ++numberOfTracks;
    }
    if (maxNumberOfTracks > 0 && numberOfTracks == maxNumberOfTracks) {
      break;
    }
  }
  return result;
} // getTrackList()
//--------------------------------------------------------------------------------------------------
public Filter or(final ETrackKeyType keyType, final EOperator operator) throws ITunesException {
  _condition.addNode(0, 0, EConjunction.Or, keyType, operator);
  return this;
} // or()
//--------------------------------------------------------------------------------------------------
public Filter or(final ETrackKeyType keyType, final EOperator operator, final long... operands)
  throws ITunesException {
  _condition.addNode(0, 0, EConjunction.Or, keyType, operator, operands);
  return this;
} // or()
//--------------------------------------------------------------------------------------------------
public Filter or(final ETrackKeyType keyType, final EOperator operator, final String... operands)
  throws ITunesException {
  _condition.addNode(0, 0, EConjunction.Or, keyType, operator, operands);
  return this;
} // or()
//--------------------------------------------------------------------------------------------------
public Filter orCloseParens(final int closeParens, final ETrackKeyType keyType, final EOperator operator)
  throws ITunesException {
  _condition.addNode(0, closeParens, EConjunction.Or, keyType, operator);
  return this;
} // orCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter orCloseParens(final int closeParens, final ETrackKeyType keyType,
                            final EOperator operator, final long... operands)
  throws ITunesException {
  _condition.addNode(0, closeParens, EConjunction.Or, keyType, operator, operands);
  return this;
} // orCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter orCloseParens(final int closeParens, final ETrackKeyType keyType,
                            final EOperator operator, final String... operands)
  throws ITunesException {
  _condition.addNode(0, closeParens, EConjunction.Or, keyType, operator, operands);
  return this;
} // orCloseParens()
//--------------------------------------------------------------------------------------------------
public Filter orOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator)
  throws ITunesException {
  _condition.addNode(openParens, 0, EConjunction.Or, keyType, operator);
  return this;
} // orOpenParens()
//--------------------------------------------------------------------------------------------------
public Filter orOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator,
                           final long... operands) throws ITunesException {
  _condition.addNode(openParens, 0, EConjunction.Or, keyType, operator, operands);
  return this;
} // orOpenParens()
//--------------------------------------------------------------------------------------------------
public Filter orOpenParens(final int openParens, final ETrackKeyType keyType, final EOperator operator,
                           final String... operands) throws ITunesException {
  _condition.addNode(openParens, 0, EConjunction.Or, keyType, operator, operands);
  return this;
} // orOpenParens()
//--------------------------------------------------------------------------------------------------
public Filter sortUsing(final ETrackKeyType... keyTypes) {
  for (ETrackKeyType keyType : keyTypes) {
    sortUsing(keyType, true);
  }
  return this;
} // sortUsing()
//--------------------------------------------------------------------------------------------------
public Filter sortUsing(final ETrackKeyType keyType, final boolean ascending) {
  _sortDefList.add(new SortDef(keyType, ascending));
  return this;
} // sortUsing()
//--------------------------------------------------------------------------------------------------
@Override
public String toString() {
  return _condition == null ? "No Condition" : _condition.toString();
} // toString()
//--------------------------------------------------------------------------------------------------
/**
 * Validates the filter conditions and throws an exception if the conditions are not valid (e.g.,
 * there are more open parentheses than close parentheses).
 */
public void validate() {

} // validate()
//--------------------------------------------------------------------------------------------------
}